const fs = require("fs");
const path = require("path");
const Thenjs = require("./then");

class Search {
    constructor(dirPathList, config) {
        // 目录下所有文件
        this.list = [];

        // 路径映射
        this.map = {};

        // 数据是否准备好
        this.ready = false;

        // 待执行队列
        this.queue = [];

        this.config = config || {};

        // 已扫描的目录
        this.history = new Set();

        this.consume = 0;
        this.lastUpdate = new Date().getTime();

        // 载入历史记录
        this.loadDB();

        // 请求扫描的目录
        this.dirPathList = new Set();
        this.setDirPathList(dirPathList);
        this.addPath();
    }

    loadDB() {
        if (this.config.dataBase) {
            if (fs.existsSync(this.config.dataBase)) {
                try {
                    let data = fs.readFileSync(this.config.dataBase);
                    data = JSON.parse(data);
                    this.history = new Set(data.history);
                    this.list = data.list;
                    this.map = data.map || {};
                    this.consume = data.consume;
                    this.lastUpdate = data.lastUpdate;
                }
                catch (err) { }
            }
        }
    }

    writeDB() {
        this.lastUpdate = new Date().getTime();
        // if (this.config.dataBase) {
        //     fs.writeFile(this.config.dataBase, JSON.stringify({
        //         map: this.map,
        //         list: this.list,
        //         history: [...this.history],
        //         lastUpdate: this.lastUpdate,
        //         consume: this.consume
        //     }), () => {});
        // }
    }

    addPathSync(dirPathList) {
        if (!dirPathList) {
            if (!Array.isArray(dirPathList)) {
                dirPathList = [dirPathList];
            }
            dirPathList.forEach((dirPath) => {
                this.dirPathList.add(path.resolve(dirPath));
            });
        }
        let begin = new Date().getTime();
        let size = this.history.size;
        fileDisplay(this.dirPathList, this.history, (file) => {
            this.list.push(file);
            this.map[path.join(file.path, file.name)] = this.list.length - 1;
        });
        if (this.history > size) {
            this.consume = new Date().getTime() - begin;
        }
        this.writeDB();
    }

    addPath(dirPathList, callback) {
        this.ready = false;
        if (dirPathList) {
            if (!Array.isArray(dirPathList)) {
                dirPathList = [dirPathList];
            }
            dirPathList.forEach((dirPath) => {
                this.dirPathList.add(path.resolve(dirPath));
            });
        }
        let begin = new Date().getTime();
        let size = this.history.size;
        getFileList([...this.dirPathList], this.history, (file) => {
            this.list.push(file);
            this.map[path.join(file.path, file.name)] = this.list.length - 1;
        }, () => {
            // 记录消耗时间
            if (this.history > size) {
                this.consume = new Date().getTime() - begin;
            }
            // 标识数据准备好
            this.ready = true;

            // 写缓存
            this.writeDB();

            // 执行待执行队列
            while (this.queue.length != 0) {
                this.queue.pop()();
            }

        });
    }

    program(keyWord, options) {
        return this.extension(".exe", keyWord, options);
    }

    extension(extensionList, keyWord, callback, options) {
        if (!this.ready) {
            // 数据没准备好时，存入队列
            this.queue.unshift(() => {
                this.extension(extensionList, keyWord, callback, options);
            });
            return;
        }
        try {
            if (!Array.isArray(extensionList)) {
                // 如果未设置则不过滤
                extensionList = extensionList ? [extensionList] : true;
            }
            else if (extensionList.length == 0) {
                extensionList = true;
            }
            options = options || {modifiers: "i"};
            let reg = new RegExp(keyWord, options.modifiers);
            callback(this.list.filter((file) => {
                if (extensionList == true || extensionList.indexOf(file.extension) >= 0) {
                    let flag = false;
                    if (reg.test(file.name)) {
                        flag = true;
                        file.nameReg = reg.exec(file.name)[0];
                    }
                    else {
                        delete file.nameReg;
                    }
                    if (reg.test(file.path)) {
                        flag = true;
                        file.pathReg = reg.exec(file.path)[0];
                    }
                    else {
                        delete file.pathReg;
                    }
                    if (options.filter) {
                        flag = flag && !options.filter.test(file.name) && !options.filter.test(file.path);
                    }
                    return flag;
                }
                else {
                    return false;
                }
            }));
        } catch(err) {
            callback([]);
        }
    }

    refresh(callback) {
        this.ready = false;
        this.list = [];
        this.history.clear();
        this.consume = new Date().getTime();
        getFileList([...this.dirPathList], this.history, (file) => {
            this.list.push(file);
            this.map[path.join(file.path, file.name)] = this.list.length - 1;
        }, () => {
            this.consume = new Date().getTime() - this.consume;

            // 标识数据准备好
            this.ready = true;

            // 写缓存
            this.writeDB();

            callback();

            // 执行待执行队列
            while (this.queue.length != 0) {
                this.queue.pop()();
            }
        });
    }

    setDirPathList(dirPathList) {
        if (Array.isArray(dirPathList)) {
            this.dirPathList = new Set(dirPathList);
        }
        else if (dirPathList == "") {
            this.dirPathList = new Set();
        }
        else {
            this.dirPathList = new Set(dirPathList.split("\n").filter(dirPath => dirPath != ""));
        }
    } 
}

/**
 * 同步遍历
 * @param {Array} dirPathList 
 * @param {Set} history 
 * @param {function} callback 
 */
function getFileListSync(dirPathList, history, callback) {
    var stack = [...dirPathList];
    let getFile = tco((dirPath) => {
        if (history.has(dirPath)) {
            if (stack.length > 0) {
                getFile(stack.pop());
            }
        }
        else {
            let files = [];
            try {
                console.log(`扫描: ${dirPath}`);
                history.add(path.resolve(dirPath));
                fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
                files = fs.readdirSync(dirPath);
            } catch (err) { }
            files.forEach((fileName) => {
                try {
                    let filePath = path.join(dirPath, fileName);
                    if (fs.statSync(filePath).isDirectory()) {
                        stack.push(path.join(dirPath, fileName));
                    }
                    else {
                        callback({
                            name: fileName,
                            path: dirPath,
                            extension: path.extname(fileName)
                        });
                    }
                }
                catch (err) { }
            });
            if (stack.length > 0) {
                getFile(stack.pop());
            }
        }
    });
    getFile(stack.pop());
}


/**
 * 尾递归优化
 * @param {function} f 
 */
function tco(f) {
    var value;
    var active = false;
    var accumulated = [];

    return function accumulator() {
        accumulated.push(arguments);
        if (!active) {
            active = true;
            while (accumulated.length) {
                value = f.apply(this, accumulated.shift());
            }
            active = false;
            return value;
        }
    };
}

/**
 * 异步遍历
 * @param {Array} dirPathList 目标路径
 * @param {Set} history 遍历历史
 * @param {function} callback 文件记录
 * @param {function} end 完成回调
 */
function getFileList(dirPathList, history, callback, end) {

    Thenjs.each(dirPathList, (next, dirPath) => {
         // 从待执行中移除
         let set = new Set(dirPathList);
         set.delete(dirPath);
         dirPathList = [...set];

        // 判断路径是否已经被扫描过
        if (!history.has(dirPath)) {

            Thenjs(cont => {
                // 判断权限
                fs.access(dirPath, cont);

            }).then((cont) => {
                // console.log(`扫描: ${dirPath}`);
                // 将路径添加到扫描历史
                history.add(path.resolve(dirPath));
                fs.readdir(dirPath, cont);

            }).then((cont, files) => {

                Thenjs.each(files, (nextFile, fileName) => {
                    Thenjs((stat) => {
                        // 获取文件类型
                        fs.stat(path.join(dirPath, fileName), (err, stats) => {
                            err ? nextFile() : stat(null, stats);
                        });
                        
                    }).then((_, stats) => {
                        if (stats.isDirectory()) {
                            // 文件夹类型放入任务队列
                            dirPathList.push(path.join(dirPath, fileName));
                        }
                        else {
                            // 记录文件
                            callback({
                                name: fileName,
                                path: dirPath,
                                extension: path.extname(fileName)
                            });
                        }
                        nextFile();
                    }).fail((err) => {
                        console.log(dirPath, err.stack);
                        nextFile();
                    });
                }).then(() => {
                    next();
                }).fail(cont);

            }).fail(() => {
                console.log(dirPath);
                next();
            });
        }
        else {
            next();
        }
    }).then(() => {
        if (dirPathList.length != 0) {
            getFileList(dirPathList, history, callback, end);
        }
        else {
            end();
        }
    });
}

exports.Search = Search;